<!DOCTYPE html>
<html prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article# " lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>kivy-ch2-paint-app | 绿萝间</title>
<link href="../assets/css/all-nocdn.css" rel="stylesheet" type="text/css">
<link href="../assets/css/ipython.min.css" rel="stylesheet" type="text/css">
<link href="../assets/css/nikola_ipython.css" rel="stylesheet" type="text/css">
<meta name="theme-color" content="#5670d4">
<meta name="generator" content="Nikola (getnikola.com)">
<link rel="alternate" type="application/rss+xml" title="RSS" href="../rss.xml">
<link rel="canonical" href="https://muxuezi.github.io/posts/kivy-ch2-paint-app.html">
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [ ['$','$'], ["\\(","\\)"] ],
        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
        processEscapes: true
    },
    displayAlign: 'center', // Change this to 'center' to center equations.
    "HTML-CSS": {
        styles: {'.MathJax_Display': {"margin": 0}}
    }
});
</script><!--[if lt IE 9]><script src="../assets/js/html5.js"></script><![endif]--><meta name="author" content="Tao Junjie">
<link rel="prev" href="set-of-julia-in-python.html" title="set-of-julia-in-python" type="text/html">
<link rel="next" href="python-parallel-system-tools-pp4e.html" title="python-parallel-system-tools-pp4e" type="text/html">
<meta property="og:site_name" content="绿萝间">
<meta property="og:title" content="kivy-ch2-paint-app">
<meta property="og:url" content="https://muxuezi.github.io/posts/kivy-ch2-paint-app.html">
<meta property="og:description" content="画图app¶








在第一章做时钟app时，我们用了Kivy的标准部件：布局，文本框和按钮。通过这些高层次的抽象，我们能够灵活的修改部件的外观—，可以使用一整套成熟的组件，而不仅仅是单个原始图形。这种方式并非放之四海而皆准，马上你就会看到，Kivy还提供了低层的抽象工具：画点和线。









我认为做画图app是自由绘画最好的方式。我们的应用会看着有点像Windows自带的画图程">
<meta property="og:type" content="article">
<meta property="article:published_time" content="2015-06-24T13:42:36+08:00">
<meta property="article:tag" content="CHS">
<meta property="article:tag" content="ipython">
<meta property="article:tag" content="Kivy">
<meta property="article:tag" content="Python">
</head>
<body>
<a href="#content" class="sr-only sr-only-focusable">Skip to main content</a>

<!-- Menubar -->

<nav class="navbar navbar-inverse navbar-static-top"><div class="container">
<!-- This keeps the margins nice -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar" aria-controls="bs-navbar" aria-expanded="false">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="https://muxuezi.github.io/">

                <span id="blog-title">绿萝间</span>
            </a>
        </div>
<!-- /.navbar-header -->
        <div class="collapse navbar-collapse" id="bs-navbar" aria-expanded="false">
            <ul class="nav navbar-nav">
<li>
<a href="../archive.html">Archive</a>
                </li>
<li>
<a href="../categories/">Tags</a>
                </li>
<li>
<a href="../rss.xml">RSS feed</a>

                
            </li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
    <a href="kivy-ch2-paint-app.ipynb" id="sourcelink">Source</a>
    </li>

                
            </ul>
</div>
<!-- /.navbar-collapse -->
    </div>
<!-- /.container -->
</nav><!-- End of Menubar --><div class="container" id="content" role="main">
    <div class="body-content">
        <!--Body content-->
        <div class="row">
            
            
<article class="post-text h-entry hentry postpage" itemscope="itemscope" itemtype="http://schema.org/Article"><header><h1 class="p-name entry-title" itemprop="headline name"><a href="#" class="u-url">kivy-ch2-paint-app</a></h1>

        <div class="metadata">
            <p class="byline author vcard"><span class="byline-name fn">
                    Tao Junjie
            </span></p>
            <p class="dateline"><a href="#" rel="bookmark"><time class="published dt-published" datetime="2015-06-24T13:42:36+08:00" itemprop="datePublished" title="2015-06-24 13:42">2015-06-24 13:42</time></a></p>
            
        <p class="sourceline"><a href="kivy-ch2-paint-app.ipynb" id="sourcelink">Source</a></p>

        </div>
        

    </header><div class="e-content entry-content" itemprop="articleBody text">
    <div tabindex="-1" id="notebook" class="border-box-sizing">
    <div class="container" id="notebook-container">

<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h2 id="画图app">画图app<a class="anchor-link" href="kivy-ch2-paint-app.html#%E7%94%BB%E5%9B%BEapp">¶</a>
</h2>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>在第一章做时钟app时，我们用了Kivy的标准部件：布局，文本框和按钮。通过这些高层次的抽象，我们能够灵活的修改部件的外观—，可以使用一整套成熟的组件，而不仅仅是单个原始图形。这种方式并非放之四海而皆准，马上你就会看到，Kivy还提供了低层的抽象工具：画点和线。</p>
<!-- TEASER_END-->
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我认为做画图app是自由绘画最好的方式。我们的应用会看着有点像Windows自带的画图程序。</p>
<p>不同的是，我们的画图app支持多平台，包括Andorid和iOS。我们也忽略了图像处理的功能，像矩形选框，图层，保存文件等。这些功能可以自己练习。</p>
<blockquote>
<p>关于移动设备：Kivy完全支持iOS开发，即使你没有类似开发经验也不难。因此，建议你先在熟悉的平台上快速实现app，这样就可以省略编译的时间和一堆细节。Android开发更简单，由于<a href="https://%0Aplay.google.com/store/apps/details?id=org.%0Akivy.pygame">Kivy Launcher</a>可以让Kivy代码直接在Android上运行。
Kivy可以不用编译直接在Andorid上运行测试，相当给力，绝对RAD（rapid application development）。
窗口改变大小的问题，并没有广泛用于移动设备，Kivy应用在不同的移动设备和桌面系统平台使用类似的处理方式。因此，开始编写和调试都非常容易，直到版本确定的最后阶段才需要集中精力弥补这些问题。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们还会学习Kivy中两个相反的功能：触摸屏的多点触控和桌面系统的鼠标点击。</p>
<p>作为移动设备的第一大法，Kivy为多点触控输入提供了一个模拟层，可以使用鼠标。可以通过右键激活功能。但是，这个多点触控模拟器并不适合真实的场景，仅适合调试用。</p>
<p>画图app最会这这样：</p>
<p><img src="kbpic/2.1paintapp.png" alt="paintapp"></p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="设置画板">设置画板<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%AE%BE%E7%BD%AE%E7%94%BB%E6%9D%BF">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们的app通过root部件自动覆盖全局，整个屏幕都可以画画。到后面增加工具按钮的时候再调整。</p>
<p>root部件是处于最外层，每个Kivy的app都有一个，可以根据app的需求制定任何部件作为root部件。比如上一章的时钟app，<code>BoxLayout</code>就是root部件；如果没其他要求，布局部件就是用来包裹其他控件的。</p>
<p>现在这个画图app，我们需要root部件具有更多的功能；用户应该可以画线条，支持多点触控。不过Kivy没有自带这些功能，所以我们自己建。</p>
<p>建立新部件很简单，只要继承Kivy的<code>Widget</code>类就行。如下所示：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.app</span> <span class="k">import</span> <span class="n">App</span>
<span class="kn">from</span> <span class="nn">kivy.uix.widget</span> <span class="k">import</span> <span class="n">Widget</span>

<span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
   <span class="k">pass</span>

<span class="k">class</span> <span class="nc">PaintApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
   <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
       <span class="k">return</span> <span class="n">CanvasWidget</span><span class="p">()</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
   <span class="n">PaintApp</span><span class="p">()</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这就是画图app的<code>main.py</code>，<code>PaintApp</code>类就是应用的起点。以后我们不会重复这些代码，只把重要的部分显示出来。</p>
<blockquote>
<p><code>Widget</code>类通常作为基类，就行Python的<code>object</code>和Java的<code>Object</code>。当它按照<code>as is</code>方式使用时，<code>Widget</code>功能极少。它没有可以直接拿来用的可视化的外观和属性。<code>Widget</code>的子类都是很简单易用的。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="制作好看的外观">制作好看的外观<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%88%B6%E4%BD%9C%E5%A5%BD%E7%9C%8B%E7%9A%84%E5%A4%96%E8%A7%82">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>首先，让我们做个好看的外观，虽然不是核心功能，但长相影响第一印象。下面我们改改外观，包括窗口大小，鼠标形状。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="可视化外观">可视化外观<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%96%E8%A7%82">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我认为任何画图软件的背景色都应该是白的。和第一章类似，我们在<code>__name = '__main__'</code>后面加上就行：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.core.window</span> <span class="k">import</span> <span class="n">Window</span>
<span class="kn">from</span> <span class="nn">kivy.utils</span> <span class="k">import</span> <span class="n">get_color_from_hex</span>

<span class="n">Window</span><span class="o">.</span><span class="n">clearcolor</span> <span class="o">=</span> <span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#FFFFFF'</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>你可能想把<code>import</code>语句放到前面，其实Kivy的一些模块导入有顺序要求，且会产生副作用，尤其是<code>Window</code>对象。这在好的Python程序中很少见，导入模块产生的副作用有点小问题。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="窗口大小">窗口大小<a class="anchor-link" href="kivy-ch2-paint-app.html#%E7%AA%97%E5%8F%A3%E5%A4%A7%E5%B0%8F">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>另一个要改的就是窗口大小，下面的改变不影响移动设备。在桌面系统上，Kivy的窗口时可以调整的，后面我们会设置禁止调整。</p>
<blockquote>
<p>如果目标设备明确，设置窗口大小是很有用的，这样就可以决定屏幕分辨率的参数，实现最好的适配效果。</p>
</blockquote>
<p>要改变窗口大小，就把下面的代码放到<code>from kivy.core.window import Window</code>上面。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.config</span> <span class="k">import</span> <span class="n">Config</span>

<span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'graphics'</span><span class="p">,</span> <span class="s1">'width'</span><span class="p">,</span> <span class="s1">'960'</span><span class="p">)</span>
<span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'graphics'</span><span class="p">,</span> <span class="s1">'height'</span><span class="p">,</span> <span class="s1">'540'</span><span class="p">)</span>  <span class="c1"># 16:9</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>如果要禁止窗口调整：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'graphics'</span><span class="p">,</span> <span class="s1">'resizable'</span><span class="p">,</span> <span class="s1">'0'</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>如果没有充分理由，千万别这么做，因为把窗口调整这点小自由从用户手中拿走实在太伤感情了。如果把应用像素精确到1px，移动设备用户可能就不爽了，而Kivy布局可以建立自适应的界面。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="鼠标样式">鼠标样式<a class="anchor-link" href="kivy-ch2-paint-app.html#%E9%BC%A0%E6%A0%87%E6%A0%B7%E5%BC%8F">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>之后就是改变鼠标光标的样式。Kivy没有支持，不过可以过Pygame实现，基于SDL窗口和OpenGL内容管理模块，在Kivy的桌面平台应用开发中用途广泛。如果你这么用，移动应用大都不支持Pygame。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>之后就是改变鼠标光标的样式。Kivy没有支持，不过可以过Pygame实现，基于SDL窗口和OpenGL内容管理模块，在Kivy的桌面平台应用开发中用途广泛。如果你这么用，移动应用大都不支持Pygame。
<img src="kbpic/2.2mousepointer.png" alt="mousepointer"></p>
<p>图中<code>@</code>是黑的，<code>-</code>是白的，其他字符是透明的。所以的线都是等宽的，且是8的倍数（SDL的限制）。鼠标的光标运行后是这样：
<img src="kbpic/2.3crosshair.png" alt="crosshair"></p>
<blockquote>
<p>当前的Pygame版本有个bug，<code>pygame.cursors.compile()</code>黑白显示颠倒。以后应该会修复。不过<code>pygame_compile_cursor()</code>是正确的方法，<a href="http://goo.gl/2KaepD">Pygame的Simple DirectMedia Layer (SDL)兼容库</a>。</p>
</blockquote>
<p>现在，我们把光标应用到app中，替换<code>PaintApp.build</code>方法：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.base</span> <span class="k">import</span> <span class="n">EventLoop</span>
<span class="k">class</span> <span class="nc">PaintApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">EventLoop</span><span class="o">.</span><span class="n">ensure_window</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">EventLoop</span><span class="o">.</span><span class="n">window</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s1">'Pygame'</span><span class="p">):</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="kn">from</span> <span class="nn">pygame</span> <span class="k">import</span> <span class="n">mouse</span>
                <span class="c1"># pygame_compile_cursor is a fixed version of</span>
                <span class="c1"># pygame.cursors.compile</span>
                <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">pygame_compile_cursor</span><span class="p">()</span>
                <span class="n">mouse</span><span class="o">.</span><span class="n">set_cursor</span><span class="p">((</span><span class="mi">24</span><span class="p">,</span> <span class="mi">24</span><span class="p">),</span> <span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">),</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
            <span class="k">except</span><span class="p">:</span>
                <span class="k">pass</span>
        <span class="k">return</span> <span class="n">CanvasWidget</span><span class="p">()</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>代码很简单，注意下面四点：</p>
<ul>
<li>
<code>EventLoop.ensure_window()</code>: 这个函数到app窗口 ( <code>EventLoop.window</code> ) 准备好才执行。</li>
<li>
<code>EventLoop.window.__class__.__name__.endswith('Pygame')</code>:
这个条件检查窗口名称Pygame，只是Pygame条件下才执行自定义光标。</li>
<li>
<code>try ... except</code>模块里面是Pygame的<code>mouse.set_cursor</code>。</li>
<li>变量<code>a</code>和<code>b</code>通过SDL构建了光标，表示异或(XOR)和与(AND)，都是SDL独有的实现方式。<blockquote>
<p><a href="http://www.pygame.org">Pygame文档</a>提供了全部的api说明。</p>
</blockquote>
</li>
</ul>
<p>现在做的这些比Kivy的模块更底层，并不常用，不过也不用害怕触及更多的细节。有很多功能只能通过底层的模块实现，因为Kivy还没达到面面俱到的程度。尤其是那些不能跨平台的功能，会涉及很多系统层的实现。</p>
<p>Kivy/Pygame/SDL/OS的关系如下图所示：
<img src="kbpic/2.4multiapi.png" alt="multiapi"></p>
<p>SDL已经把系统底层的API都封装好了，兼容多个系统，Pygame再将SDL转换成Python，Kivy可以导入Pygame模块调用这些功能。</p>
<blockquote>
<p>为什么不直接用SDL呢？可以看<a href="https://www.libsdl.org/">SDL文档</a>。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="多点触控模拟器">多点触控模拟器<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%A4%9A%E7%82%B9%E8%A7%A6%E6%8E%A7%E6%A8%A1%E6%8B%9F%E5%99%A8">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>让运行桌面应用时，Kivy提供了一个模拟器实现多点触控操作。实际上是一个右击行为，获取半透明的点；按住右键时可以拖拽。</p>
<p>如果你没有真实的多点触控设备，这个功能可能适合调试。但是，也会占用右键的功能。不调试的时候还是建议你禁用这个功能，避免对用户造成困扰。设置方法如下：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="s1">'mouse'</span><span class="p">,</span> <span class="s1">'mouse,disable_multitouch'</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="触摸绘画">触摸绘画<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%A7%A6%E6%91%B8%E7%BB%98%E7%94%BB">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>要实现用户通过触摸绘画的效果，可以在用户输入后屏幕会出现一个圆圈。</p>
<p>部件如果带<code>on_touch_down</code>事件，就可以实现上述功能。正在需要的是点击位置的坐标，为<code>CanvasWidget</code>添加一个方法获取即可：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>要在屏幕上画画，我们就要实现<code>Widget.canvas</code>属性。Kivy的<code>canvas</code>属性是一个底层为OpenGL的可绘制层，不过没有底层图形API那么复杂，<code>canvas</code>可以持续保留我们画过的图。</p>
<p>基本图形如圆（Color），线（Line）, 矩形（Rectangle），贝塞尔曲线（Bezier），可以通过<code>kivy.graphics</code>导入。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="canvas简介">canvas简介<a class="anchor-link" href="kivy-ch2-paint-app.html#canvas%E7%AE%80%E4%BB%8B">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p><code>Canvas</code>的API可以直接调用，也可以通过上下文关联<code>with</code>关键字调用。如下所示：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Line</span><span class="p">(</span><span class="n">circle</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">25</span><span class="p">)))</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这里的<code>Line</code>元素的参数是图形命令队列。</p>
<blockquote>
<p>如果你想立刻试验代码，请先看下一节<strong>屏幕显示触摸轨迹</strong>中更完整的例子。</p>
</blockquote>
<p>通过上下文关联with关键字调用可以让代码更简练，尤其是在同时操作多个指令时。下面的代码与之前一致：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
    <span class="n">Line</span><span class="p">(</span><span class="n">circle</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">25</span><span class="p">))</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>需要注意的是，如前面所说，canvas上后面调用的指令不会覆盖前面调用的指令；因此，canvas是一个不断增长的数组，里面都是不断显示元素的指令，更新频率60fps，但是也不能让canvas无限增长下去。</p>
<p>例如，所见即所得的程序（如HTML5的<code>&lt;canvas&gt;</code>）里有一条设计规则就是通过背景色填充擦除之前的图像。在浏览器里面可以很直观的写出：</p>
<div class="highlight"><pre><span></span><span class="c1">// JavaScript code for clearing the canvas</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">rect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">width</span><span class="p">,</span> <span class="nx">height</span><span class="p">)</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="s1">'#FFFFFF'</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">fill</span><span class="p">()</span>
</pre></div>
<p>在Kivy设计中，这种模型也是增加指令；首先获取前面所有的图形元素，然后把它们画成矩形。这个看着挺好其实不对：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># 看着和avaScript代码一样，但是错了。</span>
<span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
    <span class="n">Color</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">Rectangle</span><span class="p">(</span><span class="n">pos</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">pos</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<blockquote>
<p>和内存泄露差不多，这个bug很久没被发现，使代码冗余，性能降低。由于显卡加速的功能，包括智能手机运行速度都很快。所以很难意识到这是一个bug。为了清除Kivy的canvas，应该用<code>canvas.clear()</code>来清除所有指令，后面会介绍。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="屏幕显示触摸轨迹">屏幕显示触摸轨迹<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%B1%8F%E5%B9%95%E6%98%BE%E7%A4%BA%E8%A7%A6%E6%91%B8%E8%BD%A8%E8%BF%B9">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们马上做一个按钮来清屏；现在让我们把触摸的轨迹显示出来。让我们把<code>print()</code>删掉，然后增加一个方法在<code>CanvasWidget</code>下面：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
        <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
            <span class="n">Color</span><span class="p">(</span><span class="o">*</span><span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#0080FF80'</span><span class="p">))</span>
            <span class="n">Line</span><span class="p">(</span><span class="n">circle</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="n">width</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这样就每次都会画一个空心圆在画布上。<code>Color</code>指令为<code>Line</code>取色。</p>
<blockquote>
<p>注意<code>hex('#0080FF80')</code>并不是CSS颜色格式，因为它有四个组成部分，表示alpha值，即透明度。类似于<code>rgb()</code>与<code>rgba()</code>的区别。</p>
</blockquote>
<p>可能你会觉得奇怪，我们用<code>Line</code>画的是圈，而不是直线。Kivy的图形元素具体很强的自定义功能，比如我们可以用<code>Rectangle</code>和<code>Triangle</code>画自定义的图片，用<code>source</code>参数设置即可。</p>
<p>前面的程序效果如下图所示：
<img src="kbpic/2.5Displayingtouches.png" alt="Displayingtouches">
画图app完整的代码如下：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># In main.py</span>
<span class="kn">from</span> <span class="nn">kivy.app</span> <span class="k">import</span> <span class="n">App</span>
<span class="kn">from</span> <span class="nn">kivy.config</span> <span class="k">import</span> <span class="n">Config</span>
<span class="kn">from</span> <span class="nn">kivy.graphics</span> <span class="k">import</span> <span class="n">Color</span><span class="p">,</span> <span class="n">Line</span>
<span class="kn">from</span> <span class="nn">kivy.uix.widget</span> <span class="k">import</span> <span class="n">Widget</span>
<span class="kn">from</span> <span class="nn">kivy.utils</span> <span class="k">import</span> <span class="n">get_color_from_hex</span>

<span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
        <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
            <span class="n">Color</span><span class="p">(</span><span class="o">*</span><span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#0080FF80'</span><span class="p">))</span>
            <span class="n">Line</span><span class="p">(</span><span class="n">circle</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">25</span><span class="p">),</span> <span class="n">width</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>

<span class="k">class</span> <span class="nc">PaintApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">CanvasWidget</span><span class="p">()</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
    <span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'graphics'</span><span class="p">,</span> <span class="s1">'width'</span><span class="p">,</span> <span class="s1">'400'</span><span class="p">)</span>
    <span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'graphics'</span><span class="p">,</span> <span class="s1">'height'</span><span class="p">,</span> <span class="s1">'400'</span><span class="p">)</span>
    <span class="n">Config</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="s1">'mouse'</span><span class="p">,</span> <span class="s1">'mouse,disable_multitouch'</span><span class="p">)</span>
    
    <span class="kn">from</span> <span class="nn">kivy.core.window</span> <span class="k">import</span> <span class="n">Window</span>
    <span class="n">Window</span><span class="o">.</span><span class="n">clearcolor</span> <span class="o">=</span> <span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#FFFFFF'</span><span class="p">)</span>
    <span class="n">PaintApp</span><span class="p">()</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这里没有加入鼠标光标显示的部分。<code>paint.kv</code>文件也没有了，用<code>build()</code>方法返回根部件。</p>
<p>注意<code>from kivy.core.window import Window</code>行，是由于有些模块有副作用，所有放在后面导入。<code>Config.set()</code>应该放在任何有副作用模块的前面。</p>
<p>下面，我们增加一些特性，让画图app实现我们想要的功能。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="清屏">清屏<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%B8%85%E5%B1%8F">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>到目前为止，我们清屏的做法就是重启程序。下面我们增加一个按钮来清屏。我们用上一章时钟app的按钮即可，没什么新鲜，有意思的是位置。</p>
<p>上一章时钟app里面，我们没有讨论过位置，所有部件都放在<code>BoxLayouts</code>里面。现在我们的app没有任何布局，因为根部件就是<code>CanvasWidget</code>，我们没有实现任何子部件的位置。</p>
<p>在Kivy里面，布局部件缺失表示每一个部件都可以随意设置位置和大小（类似的UI设计工具，如Delphi，Visual Basic等等都如此）。</p>
<p>要让清屏按钮放在右上角，我们这么做：</p>
<div class="highlight"><pre><span></span><span class="c1"># In paint.kv</span>
<span class="l l-Scalar l-Scalar-Plain">&lt;CanvasWidget&gt;</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">Button</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Delete'</span>
        <span class="l l-Scalar l-Scalar-Plain">right</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root.right</span>
        <span class="l l-Scalar l-Scalar-Plain">top</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root.top</span>
        <span class="l l-Scalar l-Scalar-Plain">width</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">80</span>
        <span class="l l-Scalar l-Scalar-Plain">height</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">40</span>
</pre></div>
<p>按钮的<code>right</code>和<code>top</code>属性与根部件的属性一致。我们还可以进行数学运行，如<code>root.top – 20</code>。结果很直接，<code>right</code>和<code>top</code>属性都是绝对值。</p>
<p>注意我们定义了一个<code>&lt;CanvasWidget&gt;</code>类却没有指定父类。这么做可以是因为我们在Python代码理论已经定义了一个同样的类。Kivy允许我们扩展所有的类，包括内部类，如<code>&lt;Button&gt;</code>和<code>&lt;Label&gt;</code>，以及自定义类。</p>
<p>这里体现了Kivy语言描述对象的可视化属性的一个好思路，类似于MVC设计方法，让内容与逻辑分离。同时，也更好的保持了所有Python程序的结构不变。这种Python代码与Kivy语言分离的思想让程序更容易维护。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="传递事件">传递事件<a class="anchor-link" href="kivy-ch2-paint-app.html#%E4%BC%A0%E9%80%92%E4%BA%8B%E4%BB%B6">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>如果你跟着教程看到现在，准备去按清屏键。你会发现没反应，因为还没有增加事件，所有没有反馈。所有单击按钮不会有动作，相反会在画布上留下空心圈。</p>
<p>因为所有的触摸都是发生在<code>CanvasWidget.on_touch_down</code>上，并没有传递给其他子部件，所以清屏按钮没反应。不像HTML的DOMDOM，Kivy事件不会从嵌套的元素升级为父元素显示出来。它们走另一条路，如果事件传递到父元素没有反应，才从父元素下降到子元素。</p>
<p>最直接的方式就是这样：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># 注意：不是最优代码</span>
<span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">widget</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">children</span><span class="p">:</span>
        <span class="n">widget</span><span class="o">.</span><span class="n">on_touch_down</span><span class="p">(</span><span class="n">touch</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>实际上，<code>Widget.on_touch_down</code>的默认行为有很多，所有我们可以直接调用，让代码更简练。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">Widget</span><span class="o">.</span><span class="n">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
        <span class="k">return</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>如果事件被正常处理了，<code>on_touch_down</code>这个handler返回<code>True</code>。触摸按钮会返回<code>True</code>是因为按钮响应了，然后很快的改变其外观。这就是为了取消我们的事件处理需要做的事情，当我们画圈的时候，方法的第二个行就<code>return</code>。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="清屏">清屏<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%B8%85%E5%B1%8F">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>现在我们回到清屏按钮上。其实很简单，就是下面两行：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">def</span> <span class="nf">clear_canvas</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>别忘了把事件绑定到<code>paint.kv</code>文件：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">Button</span><span class="p p-Indicator">:</span>
<span class="l l-Scalar l-Scalar-Plain">on_release</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root.clear_canvas()</span>
</pre></div>
<p>这样就可以清屏了，同时还把按钮也清除了。因为<code>CanvasWidget</code>是根部件，按钮是子部件。按钮部件本身没有被删除，它的画布<code>Button.canvas</code>从<code>CanvasWidget.canvas.children</code>层级中移除了，因此不存在了。</p>
<p>要保留按钮，可以这样：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">def</span> <span class="nf">clear_canvas</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">children</span> <span class="o">=</span> <span class="p">[</span><span class="n">widget</span><span class="o">.</span><span class="n">canvas</span> 
                            <span class="k">for</span> <span class="n">widget</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">children</span><span class="p">]</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>但是这么做不够好，因为不同的部件初始化和运行方式不同。更好的做法是：</p>
<ol>
<li>从<code>CanvasWidget</code>部件中删除所有子部件；</li>
<li>然后清除画布；</li>
<li>最后再重新增加子部件，这样它们就可以正确的初始化了。</li>
</ol>
<p>这个版本有点长，但是更合理：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">clear_canvas</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">saved</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">children</span><span class="p">[:]</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">clear_widgets</span><span class="p">()</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">widget</span> <span class="ow">in</span> <span class="n">saved</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">add_widget</span><span class="p">(</span><span class="n">widget</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>解释一下<code>saved = self.children[:]</code>语句。<code>[:]</code>操作符是复制数组（就是“创建一个元素相同的数组”）。如果我们写<code>saved = self.children</code>，那就会从<code>self.children</code>和<code>saved</code>同时删除所有子部件。因为Python赋值是引用，与Kivy无关。</p>
<blockquote>
<p>如果想进一步了解Python的特性，可以看看<a href="http://stackoverflow.com/%0Aquestions/509211">StackOverflow</a></p>
</blockquote>
<p>现在，我们已经可以用蓝色的圈钱画图了，如下所示。这当然并非最终版，请看下面的内容。
<img src="kbpic/2.6Deletebutton.png" alt="Deletebutton"></p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="连点成线">连点成线<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%BF%9E%E7%82%B9%E6%88%90%E7%BA%BF">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们的app已经可以清屏了，不过只能画圈。下面在改进一下。</p>
<p>要保持连续触控画线（按住然后拖拽），我们需增加一个监听器，<code>on_touch_move</code>。每次使用都会收到最新点的位置。</p>
<p>如果我们一次只有一条线，我们可以把这条线保存为<code>self.current_line</code>。但是，由于这是多点触控，我们就要用其他方法来保存<code>touch</code>变量了。</p>
<p>之所以能实现这些，是因为每个触控自始至终都访问相同的<code>touch</code>对象。还有一个<code>touch.ud</code>属性，是一个字典类型，<code>ud</code>就是用户数据(user data)，可以灵活的跟踪所有的触控。初始值为空字典<code>{}</code>。</p>
<p>下面我们要做的是：</p>
<ul>
<li>在<code>on_touch_down</code>的handler创建一个新线，然后储存到<code>touch.ud</code>。现在我们要用直线来代替空心圈。</li>
<li>在<code>on_touch_move</code>里面增加一个新点到线的末尾。我们增加的是直线元素，但是事件处理过程是每秒调用很多次实现这条线，每次都很短，最终看起来就很平滑。</li>
</ul>
<blockquote>
<p>更先进的图形程序可以用复杂的算法让线条呈现的更真实。包括贝塞尔曲线实现线条的高分辨率的无缝连接，并且从点的速度和压力推断线的厚度。这些具体的技术我们不打算实现了，不过读者可以作为一个练习。</p>
</blockquote>
<p>上述过程的代码如下：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [4]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.graphics</span> <span class="k">import</span> <span class="n">Color</span><span class="p">,</span> <span class="n">Line</span>
    <span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">Widget</span><span class="o">.</span><span class="n">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
                <span class="k">return</span>
            <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
                <span class="n">Color</span><span class="p">(</span><span class="o">*</span><span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#0080FF80'</span><span class="p">))</span>
                <span class="n">touch</span><span class="o">.</span><span class="n">ud</span><span class="p">[</span><span class="s1">'current_line'</span><span class="p">]</span> <span class="o">=</span> <span class="n">Line</span><span class="p">(</span>
                    <span class="n">points</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">),</span> <span class="n">width</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">on_touch_move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
            <span class="k">if</span> <span class="s1">'current_line'</span> <span class="ow">in</span> <span class="n">touch</span><span class="o">.</span><span class="n">ud</span><span class="p">:</span>
                <span class="n">touch</span><span class="o">.</span><span class="n">ud</span><span class="p">[</span><span class="s1">'current_line'</span><span class="p">]</span><span class="o">.</span><span class="n">points</span> <span class="o">+=</span> <span class="p">(</span>
                    <span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这样就可以画线了。之后让我们来实现颜色选择功能，不断的完善我们的画图app。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="调色板">调色板<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%B0%83%E8%89%B2%E6%9D%BF">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>画图app当然不能没有调色板。调色板其实就是可选颜色列表，可以让颜色选取很简单。通过图像编辑器都有调色板，带有全真彩24位色16,777,216种。如下图所示：
<img src="kbpic/2.7colorpalettewindow.png" alt="colorpalettewindow">
但是，就是你不打算完成一个主流的图像编辑器，我们也打算限制颜色的种类。因为对那些没有色彩常识的人来说，放一堆颜色只会让人头大。而且，互联网上的UI设计用色也会逐渐统一。</p>
<p>在我们的app中，我们打算使用<a href="http://designmodo.github.io/Flat-UI/">扁平化的UI设计风格</a>，基于一列精心挑选的颜色。当然，你可以选自己喜欢的颜色，因人而异。</p>
<blockquote>
<p>颜色是一门学问，尤其是具体任务的兼容性与稳定性。低对比度的组合可能用来装饰元素或者标题，但是它们不符合正文的风格；另外，高对比度的颜色，如白与黑，不容易吸引注意力。</p>
<p>因此，颜色使用的首要原则是除非你很专业，否则用别人调好的颜色。最好的起点就是操作系统的用色。一些精彩案例如下：</p>
<ul>
<li>
<a href="http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines">Tango调色板</a>，在Linux开源环境中使用广泛。</li>
<li>Google在2014年GoogleIO大会上发布的<a href="https://www.google.com/design/material-design.pdf">Material design</a>。</li>
<li>非官方的<a href="http://ios7colors.com/">iOS 7颜色风格</a>，超赞。</li>
</ul>
<p>还有很多调色板可以学习，自行Google之。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="按钮的子类">按钮的子类<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%8C%89%E9%92%AE%E7%9A%84%E5%AD%90%E7%B1%BB">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>因为我使用的颜色很少，所以用单选按钮就可以了。Kivy的<code>ToggleButton</code>可以实现功能，不过有个限制：在一个单选组内，所有的按钮可以同时不选。也就是说，画图的时候可能没颜色。当然我们也可以设定默认颜色，但是用户可能会觉得很奇怪，所有我们不打算这么用。</p>
<p>Python的OOP模式可以很好的解决这个问题，我们可以继承<code>ToggleButton</code>类，然后改造它的功能。之后，每次都会有一个颜色被选中了。</p>
<p>子类还会实现另外一个功能：在调色板上，我们想让每个颜色按钮有唯一颜色。我们可以用之前的技术为每个按钮分配背景色，那就要一堆代码来分配。但是，我们如果写一个背景色属性，就可以在<code>paint.kv</code>文件里面分配了。</p>
<p>这样就可以在<code>paint.kv</code>文件中使用按钮时保持调色板定义的可读性，同时在子类中实现的具体的细节——会展示OOP程序应该怎样实现。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="去掉全不选功能">去掉全不选功能<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%8E%BB%E6%8E%89%E5%85%A8%E4%B8%8D%E9%80%89%E5%8A%9F%E8%83%BD">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>首先，让我们把全不选的功能去掉。</p>
<p>首先，让我们实现一个标准的<code>ToggleButton</code>部件。我们之间在<code>paint.kv</code>文件里面增加如下代码：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">BoxLayout</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">orientation</span><span class="p p-Indicator">:</span> <span class="s">'horizontal'</span>
    <span class="l l-Scalar l-Scalar-Plain">padding</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">3</span>
    <span class="l l-Scalar l-Scalar-Plain">spacing</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">3</span>
    <span class="l l-Scalar l-Scalar-Plain">x</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">0</span>
    <span class="l l-Scalar l-Scalar-Plain">y</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">0</span>
    <span class="l l-Scalar l-Scalar-Plain">width</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root.width</span>
    <span class="l l-Scalar l-Scalar-Plain">height</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">40</span>

    <span class="l l-Scalar l-Scalar-Plain">ToggleButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">group</span><span class="p p-Indicator">:</span> <span class="s">'color'</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Red'</span>

    <span class="l l-Scalar l-Scalar-Plain">ToggleButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">group</span><span class="p p-Indicator">:</span> <span class="s">'color'</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Blue'</span>
        <span class="l l-Scalar l-Scalar-Plain">state</span><span class="p p-Indicator">:</span> <span class="s">'down'</span>
</pre></div>
<p>我们用了与<code>BoxLayout</code>类似的方式，每个颜色按钮单独分配一个工具栏。布局部件本文的位置是绝对的，其<code>x</code>和<code>y</code>的值都是0，也就是左下角，宽度与<code>CanvasWidget</code>一致。</p>
<p>每个<code>ToggleButton</code>都属于同一<code>color</code>组。因此同一时间只有一个颜色可以被选中。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h5 id="改写标准行为">改写标准行为<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%94%B9%E5%86%99%E6%A0%87%E5%87%86%E8%A1%8C%E4%B8%BA">¶</a>
</h5>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>要实现改写，让我们定义<code>ToggleButton</code>子类：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.uix.behaviors</span> <span class="k">import</span> <span class="n">ToggleButtonBehavior</span>
<span class="kn">from</span> <span class="nn">kivy.uix.togglebutton</span> <span class="k">import</span> <span class="n">ToggleButton</span>

<span class="k">class</span> <span class="nc">RadioButton</span><span class="p">(</span><span class="n">ToggleButton</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">_do_press</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="s1">'normal'</span><span class="p">:</span>
            <span class="n">ToggleButtonBehavior</span><span class="o">.</span><span class="n">_do_press</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这样当按下按钮，状态<code>'normal'</code>就会变成<code>'down'</code>。</p>
<p>现在我们把<code>paint.kv</code>文件里面<code>ToggleButton</code>改成<code>RadioButton</code>，立刻就会看到不同。</p>
<p>这也是Kivy框架最吸引人的地方：小代码实现大功能。</p>
<blockquote>
<p>要在Kivy语言中使用<code>RadioButton</code>，其定义需要在导入<code>main.py</code>文件。由于现在只有一个Python文件，这并不重要，但是一定记住：自定义的Kivy部件，和其他的Python类和函数一样，需要在使用之前被导入。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="彩色按钮">彩色按钮<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%BD%A9%E8%89%B2%E6%8C%89%E9%92%AE">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>现在按钮的功能正常了，我们把彩色按钮都做出来。如下图所示：
<img src="kbpic/2.8Paintappcolorpalette.png" alt="Paintappcolorpalette">
要实现这些，我们得用<code>background_color</code>属性。Kivy的背景色不仅可以使用单一颜色，可以用彩色；我们首先需要一个纯白色背景，然后画上想要的颜色。这样我们就只要为任意数量的彩色按钮准备两种模式（正常和按下的）即可。</p>
<p>这和第一章时钟app是一样的。除了按钮的中心区域允许着色，选中的状态有个黑边。
<img src="kbpic/2.9colorbuttontexture.png" alt="colorbuttontexture"></p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h5 id="新按钮">新按钮<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%96%B0%E6%8C%89%E9%92%AE">¶</a>
</h5>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>加油！我们就快完工了，在<code>paint.kv</code>里面加入新类<code>ColorButton</code>：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">&lt;ColorButton@RadioButton&gt;</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">group</span><span class="p p-Indicator">:</span> <span class="s">'color'</span>
    <span class="l l-Scalar l-Scalar-Plain">on_release</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">app.canvas_widget.set_color(self.background_color)</span>
    <span class="l l-Scalar l-Scalar-Plain">background_normal</span><span class="p p-Indicator">:</span> <span class="s">'color_button_normal.png'</span>
    <span class="l l-Scalar l-Scalar-Plain">background_down</span><span class="p p-Indicator">:</span> <span class="s">'color_button_down.png'</span>
    <span class="l l-Scalar l-Scalar-Plain">border</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">(3, 3, 3, 3)</span>
</pre></div>
<p>你会发现，我们把<code>group: 'color'</code>移到这里避免重复代码。</p>
<p>我们还要配置<code>on_release</code>事件handler，作用于已经被选中的按钮。现在，每个按钮已经把自己的<code>background_color</code>属性传递给事件handler，剩下的事情就是把颜色分配给画布。这个事件将由<code>CanvasWidget</code>处理，需要通过<code>PaintApp</code>类显示出来。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">class</span> <span class="nc">PaintApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="c1"># set_color()方法后面实现</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">canvas_widget</span> <span class="o">=</span> <span class="n">CanvasWidget</span><span class="p">()</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">canvas_widget</span><span class="o">.</span><span class="n">set_color</span><span class="p">(</span>
            <span class="n">get_color_from_hex</span><span class="p">(</span><span class="s1">'#2980B9'</span><span class="p">))</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas_widget</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这么配置的原因是我们不能在<code>paint.kv</code>文件的类定义中使用<code>root</code>；因为那样会指向<code>ColorButton</code>自身（类规则里面的根定义在<code>paint.kv</code>文件的顶层）。我们还可以设置默认颜色，就像代码里显示的。</p>
<p>在<code>main.py</code>文件里面，让我们来实现<code>CanvasWidget</code>的<code>set_color()</code>方法，可以当作是<code>ColorButton</code>的事件handler。代码很简单，就是把颜色作为参数：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">def</span> <span class="nf">set_color</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">new_color</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Color</span><span class="p">(</span><span class="o">*</span><span class="n">new_color</span><span class="p">))</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h5 id="定义调色板">定义调色板<a class="anchor-link" href="kivy-ch2-paint-app.html#%E5%AE%9A%E4%B9%89%E8%B0%83%E8%89%B2%E6%9D%BF">¶</a>
</h5>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>下面我们来定义调色板。首先让我们把<code>RadioButton</code>从<code>paint.kv</code>文件中删掉。</p>
<p>为了使用CSS颜色定义方式，我们需要将适当的函数导入<code>paint.kv</code>文件。把下面这行代码放在<code>paint.kv</code>文件开头。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="c1">#:import C kivy.utils.get_color_from_hex</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这行代码实际上和Python的代码一样：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.utils</span> <span class="k">import</span> <span class="n">get_color_from_hex</span> <span class="k">as</span> <span class="n">C</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们使用扁平化设计的配色方式，代码如下：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">BoxLayout</span><span class="p p-Indicator">:</span>
    <span class="c1"># ...</span>
    <span class="l l-Scalar l-Scalar-Plain">ColorButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">background_color</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">C('#2980b9')</span>
        <span class="l l-Scalar l-Scalar-Plain">state</span><span class="p p-Indicator">:</span> <span class="s">'down'</span>

    <span class="l l-Scalar l-Scalar-Plain">ColorButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">background_color</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">C('#16A085')</span>

    <span class="l l-Scalar l-Scalar-Plain">ColorButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">background_color</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">C('#27AE60')</span>
</pre></div>
<p>很简单吧，这样就为每个<code>ColorButton</code>按钮定义了<code>background_color</code>属性。其他的属性都是继承于Python中<code>ColorButton</code>类的定义。</p>
<p>这样，增加任意数量的按钮都可以很好的排列了。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="设置线的宽度">设置线的宽度<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%AE%BE%E7%BD%AE%E7%BA%BF%E7%9A%84%E5%AE%BD%E5%BA%A6">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>最后一个，也是最简单的功能就是设置线条的宽度。如下图所示，我们可以重用前面调色板的资源和样式。</p>
<p>这个UI也是一种<code>RadioButton</code>子类，命名为<code>LineWidthButton</code>。在<code>paint.kv</code>文件中就是这样：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">&lt;LineWidthButton@ColorButton&gt;</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">group</span><span class="p p-Indicator">:</span> <span class="s">'line_width'</span>
    <span class="l l-Scalar l-Scalar-Plain">on_release</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">app.canvas_widget.set_line_width(self.text)</span>
    <span class="l l-Scalar l-Scalar-Plain">color</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">C('#2C3E50')</span>
    <span class="l l-Scalar l-Scalar-Plain">background_color</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">C('#ECF0F1')</span>
</pre></div>
<p>与<code>ColorButton</code>不同之处在于第2、3行代码。这些按钮属于另外一组，由其他的事件handler触发。当然，这两组按钮依然很相似。</p>
<p>布局很简单，和调色板的样式一致，只是垂直摆放：</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">BoxLayout</span><span class="p p-Indicator">:</span>
    <span class="l l-Scalar l-Scalar-Plain">orientation</span><span class="p p-Indicator">:</span> <span class="s">'vertical'</span>
    <span class="l l-Scalar l-Scalar-Plain">padding</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">2</span>
    <span class="l l-Scalar l-Scalar-Plain">spacing</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">2</span>
    <span class="l l-Scalar l-Scalar-Plain">x</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">0</span>
    <span class="l l-Scalar l-Scalar-Plain">top</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">root.top</span>
    <span class="l l-Scalar l-Scalar-Plain">width</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">80</span>
    <span class="l l-Scalar l-Scalar-Plain">height</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">110</span>

    <span class="l l-Scalar l-Scalar-Plain">LineWidthButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Thin'</span>

    <span class="l l-Scalar l-Scalar-Plain">LineWidthButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Normal'</span>
        <span class="l l-Scalar l-Scalar-Plain">state</span><span class="p p-Indicator">:</span> <span class="s">'down'</span>

    <span class="l l-Scalar l-Scalar-Plain">LineWidthButton</span><span class="p p-Indicator">:</span>
        <span class="l l-Scalar l-Scalar-Plain">text</span><span class="p p-Indicator">:</span> <span class="s">'Thick'</span>
</pre></div>
<blockquote>
<p>注意<code>CanvasWidget.set_line_width</code>事件监听器会接受宽度调节按钮的<code>text</code>属性。这样实现是为了简化，允许我们为每一个按钮定义一个唯一的宽度值。
实际开发中，这种方法固然无可厚非。但是，当我们要把文字翻译成日语或法语的时候，这种对应关系就丢失了。</p>
</blockquote>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h4 id="改变线条宽度">改变线条宽度<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%94%B9%E5%8F%98%E7%BA%BF%E6%9D%A1%E5%AE%BD%E5%BA%A6">¶</a>
</h4>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>让我们把前面做好的模块都组合起来，这样就可以控制线条的粗细了。我们把线条宽度存储在<code>CanvasWidget.line_width</code>变量中，与按钮的文字一一对应，然后用<code>on_touch_down</code>触发事件改变线条宽度。代码如下：</p>

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered">
<div class="input">
<div class="prompt input_prompt">In [ ]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="k">class</span> <span class="nc">CanvasWidget</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
    <span class="n">line_width</span> <span class="o">=</span> <span class="mi">2</span>
    <span class="k">def</span> <span class="nf">on_touch_down</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">touch</span><span class="p">):</span>
        <span class="c1"># ...</span>
        <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">canvas</span><span class="p">:</span>
            <span class="n">touch</span><span class="o">.</span><span class="n">ud</span><span class="p">[</span><span class="s1">'current_line'</span><span class="p">]</span> <span class="o">=</span> <span class="n">Line</span><span class="p">(</span>
                <span class="n">points</span><span class="o">=</span><span class="p">(</span><span class="n">touch</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="n">touch</span><span class="o">.</span><span class="n">y</span><span class="p">),</span>
                <span class="n">width</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">line_width</span><span class="p">)</span>
            
<span class="k">def</span> <span class="nf">set_line_width</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">line_width</span><span class="o">=</span><span class="s1">'Normal'</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">line_width</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s1">'Thin'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'Normal'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'Thick'</span><span class="p">:</span> <span class="mi">4</span>
    <span class="p">}[</span><span class="n">line_width</span><span class="p">]</span>
</pre></div>

</div>
</div>
</div>

</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这样就完成Kivy的画图app了，开始画图吧。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="总结">总结<a class="anchor-link" href="kivy-ch2-paint-app.html#%E6%80%BB%E7%BB%93">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>这一章，我们重点学习了Kivy应用开发中的一些方法，包括自定义窗口，改变鼠标光标，窗口大小，背景色，通过画布指令绘制自定义的图形，正确的处理支持多平台的触摸事件，并且考虑多点触控的情况。</p>
<p>在完成画图app之后，关于Kivy的一件显而易见的事情就是这个框架具有高度的开放性和通用性。不需要一大堆死板的组件，Kivy让开发者可以通过图形基本元素和行为的运用，让自定义模块变得简单灵活。也就是说，Kivy没有自带很多开箱即用的部件，但是通过几行Python代码就可以做出需要的东西。</p>
<p>模块化的API设计方法缺乏美感，因为它限制了设计的柔性。最终的结果完全的满足你对项目的需求。客户总想要一些爆点，比如三角形按钮——当然，你还可以为它增加质地，这些都可以两三行代码搞定。（假如你想用<strong>WinAPI</strong>做一个三角形按钮。那就真掉坑里了。）</p>
<p>Kivy的自定义部件还可以重用。实际上，你可以把<code>main.py</code>的<code>CanvasWidget</code>模块导入其他应用。</p>

</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h3 id="自然用户界面">自然用户界面<a class="anchor-link" href="kivy-ch2-paint-app.html#%E8%87%AA%E7%84%B6%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2">¶</a>
</h3>
</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>我们的第二个应用比第一个应用更具交互性。不仅是在按钮上，还有多点触控手势。</p>
<p>所有的窗口都支持触摸屏，对用户来说这是普遍共识，尤其在触摸屏设备上。只要用手指就可以绘画，好像在真实的画布上，即使手指很脏也可以上面画画。</p>
<p>这种界面被称为NUI(自然界面，natural user interface)。有一些有趣的特性：NUI应用可以被小朋友或者宠物使用——可以在屏幕上看到和触摸图形元素。这是一种自然、直观的界面，一种“不需要思考”的事情，与<a href="http://en.wikipedia.org/wiki/Norton_Commander">Norton Commander</a>的反直觉截然不同。直觉不应该接受蓝屏、ASCII码的表现形式。</p>
<p>下一章，我们将建立另外一个Kivy程序，只能Android用。将Python与Android API的Java类很好的结合在一起。</p>

</div>
</div>
</div>
    </div>
  </div>

    </div>
    <aside class="postpromonav"><nav><ul itemprop="keywords" class="tags">
<li><a class="tag p-category" href="../categories/chs.html" rel="tag">CHS</a></li>
            <li><a class="tag p-category" href="../categories/ipython.html" rel="tag">ipython</a></li>
            <li><a class="tag p-category" href="../categories/kivy.html" rel="tag">Kivy</a></li>
            <li><a class="tag p-category" href="../categories/python.html" rel="tag">Python</a></li>
        </ul>
<ul class="pager hidden-print">
<li class="previous">
                <a href="set-of-julia-in-python.html" rel="prev" title="set-of-julia-in-python">Previous post</a>
            </li>
            <li class="next">
                <a href="python-parallel-system-tools-pp4e.html" rel="next" title="python-parallel-system-tools-pp4e">Next post</a>
            </li>
        </ul></nav></aside><script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script><script type="text/x-mathjax-config">
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [ ['$','$'], ["\\(","\\)"] ],
        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
        processEscapes: true
    },
    displayAlign: 'center', // Change this to 'center' to center equations.
    "HTML-CSS": {
        styles: {'.MathJax_Display': {"margin": 0}}
    }
});
</script></article>
</div>
        <!--End of body content-->

        <footer id="footer">
            Contents © 2017         <a href="mailto:muxuezi@gmail.com">Tao Junjie</a> - Powered by         <a href="https://getnikola.com" rel="nofollow">Nikola</a>         
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0">
<img alt="Creative Commons License BY-NC-SA" style="border-width:0; margin-bottom:12px;" src="http://i.creativecommons.org/l/by-nc-sa/4.0/80x15.png"></a>
            
        </footer>
</div>
</div>


            <script src="../assets/js/all-nocdn.js"></script><script>$('a.image-reference:not(.islink) img:not(.islink)').parent().colorbox({rel:"gal",maxWidth:"100%",maxHeight:"100%",scalePhotos:true});</script><!-- fancy dates --><script>
    moment.locale("en");
    fancydates(0, "YYYY-MM-DD HH:mm");
    </script><!-- end fancy dates --><script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-51330059-1', 'auto');
  ga('send', 'pageview');

</script>
</body>
</html>
